﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ree7.IO.Png
{

    /// <summary>
    /// Contains a list of possible chunk type identifier.
    /// </summary>
    static class PngChunkTypes
    {
        /// <summary>
        /// The first chunk in a png file. Can only exists once. Contains 
        /// common information like the width and the height of the image or
        /// the used compression method.
        /// </summary>
        public const string Header = "IHDR";
        /// <summary>
        /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
        /// series in the RGB format.
        /// </summary>
        public const string Palette = "PLTE";
        /// <summary>
        /// The IDAT chunk contains the actual image data. The image can contains more
        /// than one chunk of this type. All chunks together are the whole image.
        /// </summary>
        public const string Data = "IDAT";
        /// <summary>
        /// This chunk must appear last. It marks the end of the PNG datastream. 
        /// The chunk's data field is empty. 
        /// </summary>
        public const string End = "IEND";
        /// <summary>
        /// This chunk specifies that the image uses simple transparency: 
        /// either alpha values associated with palette entries (for indexed-color images) 
        /// or a single transparent color (for grayscale and truecolor images). 
        /// </summary>
        public const string PaletteAlpha = "tRNS";
        /// <summary>
        /// Textual information that the encoder wishes to record with the image can be stored in 
        /// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
        /// </summary>
        public const string Text = "tEXt";
        /// <summary>
        /// This chunk specifies the relationship between the image samples and the desired 
        /// display output intensity.
        /// </summary>
        public const string Gamma = "gAMA";
        /// <summary>
        /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. 
        /// </summary>
        public const string Physical = "pHYs";
    }

    /// <summary>
    /// Stores header information about a chunk.
    /// </summary>
    sealed class PngChunk
    {
        /// <summary>
        /// An unsigned integer giving the number of bytes in the chunk's 
        /// data field. The length counts only the data field, not itself, 
        /// the chunk type code, or the CRC. Zero is a valid length
        /// </summary>
        public int Length;
        /// <summary>
        /// A chunk type as string with 4 chars.
        /// </summary>
        public string Type;
        /// <summary>
        /// The data bytes appropriate to the chunk type, if any. 
        /// This field can be of zero length. 
        /// </summary>
        public byte[] Data;
        /// <summary>
        /// A CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, 
        /// including the chunk type code and chunk data fields, but not including the length field. 
        /// The CRC is always present, even for chunks containing no data
        /// </summary>
        public uint Crc;
    }

    /// <summary>
    /// Represents the png header chunk.
    /// </summary>
    sealed class PngHeader
    {
        /// <summary>
        /// The dimension in x-direction of the image in pixels.
        /// </summary>
        public int Width;
        /// <summary>
        /// The dimension in y-direction of the image in pixels.
        /// </summary>
        public int Height;
        /// <summary>
        /// Bit depth is a single-byte integer giving the number of bits per sample 
        /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, 
        /// although not all values are allowed for all color types. 
        /// </summary>
        public byte BitDepth;
        /// <summary>
        /// Color type is a integer that describes the interpretation of the 
        /// image data. Color type codes represent sums of the following values: 
        /// 1 (palette used), 2 (color used), and 4 (alpha channel used).
        /// </summary>
        public byte ColorType;
        /// <summary>
        /// Indicates the method  used to compress the image data. At present, 
        /// only compression method 0 (deflate/inflate compression with a sliding 
        /// window of at most 32768 bytes) is defined.
        /// </summary>
        public byte CompressionMethod;
        /// <summary>
        /// Indicates the preprocessing method applied to the image 
        /// data before compression. At present, only filter method 0 
        /// (adaptive filtering with five basic filter types) is defined.
        /// </summary>
        public byte FilterMethod;
        /// <summary>
        /// Indicates the transmission order of the image data. 
        /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace).
        /// </summary>
        public byte InterlaceMethod;
    }

    /// <summary>
    /// Adds the filter byte to a raw bitmap
    /// without allocating another bitmap in RAM
    /// </summary>
    public class FilteredBitmap
    {
        const int PixelDepth = 4;

        readonly int[] _source;
        int _width;
        int _height;
        int _position;

        public FilteredBitmap(int[] pixels, int pixelWidth, int pixelHeight)
        {
            _source = pixels;
            _width = pixelWidth;
            _height = pixelHeight;
            _position = 0;
        }

        public void Seek(int position)
        {
            if (position < 0
                || position > _width * _height * PixelDepth)
            {
                throw new InvalidOperationException();
            }

            _position = position;
        }

        public bool HasNext()
        {
            return _position < Length;
        }

        public byte GetNext()
        {
            int rowLength = _width * PixelDepth + 1;
            int y = _position / rowLength;
            int x = _position % rowLength;

            _position++;

            if (x == 0) return 0;

            // Switch to the source referential (without filter byte)
            x = x - 1;

            int sourceY = y;
            int sourceX = x / PixelDepth;
            int offset = x % PixelDepth;

            int pixel = _source[sourceY * _width + sourceX];
            byte[] components = BitConverter.GetBytes(pixel);

            return components[offset];
        }

        public int Length
        {
            get
            {
                return _width * _height * PixelDepth + _height;
            }
        }
    }

}
